use strict;
package Graphics::PlanarVector::Transform;
use Exporter 'import';
our @EXPORT_OK = qw/matrix scale rotate translate matrix_translate
		matrix_rotate matrix_scale PI/;

use constant PI => 4 * atan2(1,1);
use constant DEG2R => PI/180;
use Carp;

use overload (
	'*' =>	sub {
		# M x N matrix multiplication
		# [ 0 2 4
		#   1 3 5 ]
		my ($m, $n) = @_;
		bless [	$m->[0] * $n->[0] + $m->[2] * $n->[1],
			$m->[1] * $n->[0] + $m->[3] * $n->[1],
			$m->[0] * $n->[2] + $m->[2] * $n->[3],
			$m->[1] * $n->[2] + $m->[3] * $n->[3],
			$m->[0] * $n->[4] + $m->[2] * $n->[5] + $m->[4],
			$m->[1] * $n->[4] + $m->[3] * $n->[5] + $m->[5],
		]
	},

	'""' =>	sub {
		my @s = map { 0 + sprintf("%.3f", $_)  } @{shift()};
		"[@s]"
	},
);

sub matrix {
	ref($_[0])	and return bless [@{$_[0]}];
	return bless [@_];
}

sub matrix_translate {
	croak "bad arguments: @_" unless @_ == 2;
	matrix(1, 0, 0, 1, @_);
}

sub matrix_scale {
	my ($x, $y, $h) = @_;
	if (!defined $y) {
		$y = $x
	} elsif (ref $y) {
		$h = $y;
		$y = $x;
	}
	$h	? matrix($x, 0, 0, $y, $h->[0] * (1-$x), $h->[1] * (1-$y))
		: matrix($x, 0, 0, $y, 0, 0)
}

sub matrix_rotate {
	my ($ang, $x, $y) = @_;
	ref($x) and ($x, $y) = @$x;

	$ang *= DEG2R;
	my ($c, $s) = (cos($ang), sin($ang));

	($x||$y)
		? bless [$c, $s, -$s, $c, $x*(1-$c) + $y*$s, $y*(1-$c) - $x*$s]
		: bless [$c, $s, -$s, $c, 0, 0]
}

sub scale { shift->transform(&matrix_scale) }
sub rotate { shift->transform(&matrix_rotate) }
sub translate { shift->transform(&matrix_translate) }

1;

__END__

=head1 NAME

Graphics::PlanarVector::Transform -- Two dimensional coord transformation functions.

=head1 SYNOPSIS

    use Graphics::PlanarVector::Transform
    		qw/matrix scale rotate translate
		   matrix_translate matrix_rotate matrix_scale PI/;

    $m1 = matrix(1, 0, 0, 1, 0, 0);    # identity matrix
    $m2 = matrix_rotate(30, [10, 10])  # matrix that rotates 30 degrees about
                                       # point (10, 10)

=over 4

=item B<matrix>($n1, $n2, $n3, $n4, $n5, $n6)

Produces a 2-D transformation matrix.  It transforms vector C<$v_in = [$xin, $yin]>
to C<$v_out = [$xout, $yout]> by the following matrix multiplication:

    / $xout \   / $n1  $n3  $n5 \  / $xin \
    | $yout | = | $n2  $n4  $n6 |  | $yin |
    \    0  /   \  0    0    1  /  \  0   /

=item operator 'B<*>'

Binary operator 'B<*>' is overloaded to multiply two transformation matrices.

=item B<matrix_scale>($scale_x[, $scale_y, $origin])

Produces a transformation matrix representing a scaling around $origin.  If both
$scale_y and $origin are omitted, $scale_y is the same as $scale_x while $origin
is set to (0, 0).  If $origin is provided, it must be a
L<Graphics::PlanarVector::Vector> object, and $scale_y cannot be omitted.

=item B<matrix_rotate>($angle[, POINT])

Produces a transformation matrix representing a rotation around $origin.  $angle
is given in degrees.  POINT can be either a L<Graphics::PlanarVector::Vector> object,
or a pair of numbers ($x, $y) as x-y coordinates.  If omitted, it's taken as (0, 0).

=item B<matrix_translate>($x, $y)

Produces a transformation matrix representing a translation by ($x, $y).

=item B<rotate>, B<scale> and B<translate>

These are importable functions meant to work as object methods.  If an object package
defines a C<transform> method that takes a matrix as an argument, then these three
functions, once imported, will parse arguments like the C<matrix_*> functions above and
call C<transform($matrix)> with the resulting matrix, e.g.

    $obj->rotate(45, vector(0, 0))

is in effect

    $obj->transform( matrix_rotate(45, vector(0, 0) )

=cut
